package com.baidu.health.service;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baidu.health.dao.MediaUploadDao;
import com.baidu.health.pojo.CheckChunkResult;
import com.baidu.health.util.MediaFile;
import com.baidu.health.util.R;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.*;

@Service
public class MediaUploadServiceImpl implements MediaUploadService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private MediaUploadDao mediaUploadDao;

    private final static Logger LOGGER = LoggerFactory.getLogger(MediaUploadService.class);
    //上传文件根目录
    @Value("${potenza-service-manage-media.upload-location}")
    String uploadPath;

    //文件上传注册
    public R register(String fileMd5,
                      String fileName,
                      Long fileSize,
                      String mimetype,
                      String fileExt) {
        System.out.println("register " + fileMd5
                + " fileName<" + fileName + "> fileSize<" + fileSize + ">"
                + " mimetype<" + mimetype + "> fileExt<" + fileExt + ">");

        //检查文件是否上传
        MediaFile mddiaByfileMd5 = findMddiaByfileMd5(fileMd5, fileExt);
        if (mddiaByfileMd5 != null) {
            return R.ok().put("fail", false).put("0", "已经上传过了");//不存在
        }
        //1、 得到文件的路径

        /** 文件命名规则
         * 根据文件md5得到文件路径
         * 规则：
         * 一级目录：md5的第一个字符
         * 二级目录：md5的第二个字符
         * 三级目录：md5
         * 文件名：md5+文件扩展名
         */
        String filePath = getFilePath(fileMd5, fileExt);
        //2、 不存在创建目录
        createFileFold(fileMd5);
        //创建成功则返回
        return R.ok().put("success", true).put("1", "创建目录成功");//不存在
    }

    ////检查块文件
    //before-send
    public CheckChunkResult checkchunk(String fileMd5,
                                       Integer chunk,
                                       Integer chunkSize) {
        System.out.println("Thread:" + Thread.currentThread().getId() + "检查块是否存在 checkchunk " + fileMd5 + " chunk<" + chunk + "> chunkSize<" + chunkSize + ">");
        //得到块文件所在路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //块文件的文件名称以1,2,3..序号命名，没有扩展名
        File file = new File(chunkFileFolderPath + chunk);
        //判断块文件是否存在
        boolean exists = file.exists();
        // boolean chunkFileExist = false;
        if (exists) {
            return new CheckChunkResult(true, "1", "chunk<" + chunk + ">存在", true);
        } else {
            return new CheckChunkResult(true, "2", "chunk<" + chunk + ">不存在", false);
        }
    }

    @Autowired
    HttpServletRequest request;

    //upload
    public R uploadchunk(MultipartFile file,
                         String fileMd5,
                         Integer chunk) throws IOException {
        System.out.println("Thread:" + Thread.currentThread().getId() + " 上传文件 uploadchunk " + fileMd5 + " chunk<" + chunk + ">");
        //创建块文件目录
        boolean chunkFileFolder = createChunkFileFolder(fileMd5);
        //获取块文件路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //上传该块
        File file1 = new File(chunkFileFolderPath + chunk);
        InputStream inputStream = file.getInputStream();
        FileOutputStream OutputStream = new FileOutputStream(file1);
        IOUtils.copy(inputStream, OutputStream);
        inputStream.close();
        OutputStream.close();

        //保存到相应的目录下
        return R.ok().put("success", true).put("1", "上传块成功");
    }

    //after-send-file
    public R mergechunks(String fileMd5,
                         String fileName,
                         Long fileSize,
                         String mimetype,
                         String fileExt,
                         Long userId) {
        System.out.println("合并文件 mergechunks " + fileMd5 + " fileName<" + fileName + ">");
        //1、获取块文件的路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //创建块文件
        File chunkfileFolder = new File(chunkFileFolderPath);
        //2、合并文件路径
        String filePath = getFilePath(fileMd5, fileExt);
        File file = new File(filePath);
        //获取所有分片文件  根据名称排序
        List<File> chunkFiles = getChunkFiles(chunkfileFolder);
        //合并文件
        File mergeFile = mergeFile(file, chunkFiles);
        //4、MD5的校验，合并后的文件 计算MD5与源文件MD5比较
        boolean b = checkFileMd5(mergeFile, fileMd5);
        //加密方法
        //DigestUtils.md5Hex();
        MediaFile mediaFile = new MediaFile();
        mediaFile.setFileName(fileMd5 + "." + fileExt);
        mediaFile.setFileOriginalName(fileName);
        String fileFolderRelativePath = getFileFolderRelativePath(fileMd5, fileExt);
        mediaFile.setFilePath(fileFolderRelativePath);
        mediaFile.setFileUrl(fileFolderRelativePath + mediaFile.getFileName());
        mediaFile.setFileType(fileExt);
        mediaFile.setMimeType(mimetype);
        mediaFile.setFileSize(fileSize);
        mediaFile.setFileStatus("1");
        mediaFile.setProcessStatus("0");
        mediaFile.setUploadTime(new Date());
        mediaFile.setTeacherId(userId);
        //添加到数据库
        mediaUploadDao.insert(mediaFile);
        Long fileId = mediaFile.getFileId();

        //5、异步调用-->RabbitMQ,向MQ发送视频处理消息
        rabbitTemplate.convertAndSend("", "Q1", JSON.toJSONString(mediaFile));
        return R.ok().put("success", true).put("1", "合并块成功");
    }

    //校验文件的md5值
    private boolean checkFileMd5(File mergeFile, String fileMd5) {
        if (mergeFile == null || StringUtils.isEmpty(fileMd5)) {
            return false;
        }
        InputStream mergeFileInputstream = null;
        try {
            mergeFileInputstream = new FileInputStream(mergeFile);
            //得到文件的md5
            String mergeFileMd5 = DigestUtils.md5Hex(mergeFileInputstream);
            //比较md5
            if (fileMd5.equalsIgnoreCase(mergeFileMd5)) {
                return true;
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("checkFileMd5 error,file is:{},md5 is:{}", mergeFile.getAbsoluteFile(), fileMd5);
        } finally {
            try {
                mergeFileInputstream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    //合并文件
    private File mergeFile(File mergeFile, List<File> chunkFiles) {
        try {
            RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");//read wirte
            //合并文件
            for (File chunkFile : chunkFiles) {
                RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "r");//read
                int len = -1;
                byte[] b = new byte[1024];
                while ((len = raf_read.read(b)) != -1) {
                    raf_write.write(b, 0, len);
                }
                raf_read.close();
            }
            raf_write.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return mergeFile;
    }

    private List<File> getChunkFiles(File chunkfileFolder) {
        File[] chunkFiles = chunkfileFolder.listFiles();
        List<File> chunkFileList = new ArrayList<File>(Arrays.asList(chunkFiles));
        Collections.sort(chunkFileList, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                if (Integer.parseInt(o1.getName()) > Integer.parseInt(o2.getName())) {
                    return 1;
                } else {
                    return -1;
                }
            }
        });
        return chunkFileList;
    }

    /**
     * 创建chunk目录
     *
     * @param fileMd5
     * @return
     */
    private boolean createChunkFileFolder(String fileMd5) {
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        File chunkFileFolder = new File(chunkFileFolderPath);
        if (!chunkFileFolder.exists()) {
            //创建文件夹
            boolean mkdirs = chunkFileFolder.mkdirs();
            return mkdirs;
        }
        return true;
    }

    /**
     * 根据文件md5得到文件路径
     * 规则：
     * 一级目录：md5的第一个字符
     * 二级目录：md5的第二个字符
     * 三级目录：md5
     * 文件名：md5+文件扩展名
     *
     * @param fileMd5 文件md5值
     * @param fileExt 文件扩展名
     * @return 文件路径
     */
    private String getFilePath(String fileMd5, String fileExt) {
        String filePath = uploadPath + fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) +
                "/" + fileMd5 + "/" + fileMd5 + "." + fileExt;
        return filePath;
    }

    //得到文件目录相对路径，路径中去掉根目录
    private String getFileFolderRelativePath(String fileMd5, String fileExt) {
        String filePath = fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" +
                fileMd5 + "/";
        return filePath;
    }

    /**
     * 得到文件真实目录
     *
     * @param fileMd5
     * @return
     */
    private String getFileFolderPath(String fileMd5) {
        String fileFolder = uploadPath + fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) +
                "/" + fileMd5 + "/";
        return fileFolder;
    }

    //得到块文件所在目录
    private String getChunkFileFolderPath(String fileMd5) {
        String fileChunkFolderPath = getFileFolderPath(fileMd5) + "/" + "chunks" + "/";
        return fileChunkFolderPath;
    }

    /**
     * 创建目录
     *
     * @param fileMd5
     * @return
     */
    private boolean createFileFold(String fileMd5) {
        String fileFolderPath = getFileFolderPath(fileMd5);
        File fileFolder = new File(fileFolderPath);
        if (!fileFolder.exists()) {
            //创建文件夹
            boolean mkdirs = fileFolder.mkdirs();
            return mkdirs;
        }
        return true;
    }

    /**
     * 通过md5加密字符串查找数据库是否存在
     *
     * @param fileMd5
     * @return
     */
    public MediaFile findMddiaByfileMd5(String fileMd5, String fileExt) {
        String fileName = fileMd5 + "." + fileExt;
        QueryWrapper<MediaFile> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(MediaFile::getFileName, fileName);
        List<MediaFile> mediaFiles = mediaUploadDao.selectList(wrapper);
        if (mediaFiles.size() > 0) {
            return mediaFiles.get(0);
        }
        return null;
    }
}
